package com.nulldev.util.internal.backport.httpclient_rw;

import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.function.Supplier;

import javax.net.ssl.SSLSession;

import com.nulldev.util.internal.backport.httpclient_rw.impl.websocket.RawChannel;
import com.nulldev.util.internal.backport.optionals.Optional;

/**
 * The implementation class for HttpResponse
 */
class HttpResponseImpl<T> implements HttpResponse<T>, RawChannel.Provider {

	final int responseCode;
	final Exchange<T> exchange;
	final HttpRequest initialRequest;
	final Optional<HttpResponse<T>> previousResponse;
	final HttpHeaders headers;
	final Optional<SSLSession> sslSession;
	final URI uri;
	final HttpClient.Version version;
	RawChannel rawchan;
	final HttpConnection connection;
	final Stream<T> stream;
	final T body;

	public HttpResponseImpl(HttpRequest initialRequest, Response response, HttpResponse<T> previousResponse, T body, Exchange<T> exch) {
		this.responseCode = response.statusCode();
		this.exchange = exch;
		this.initialRequest = initialRequest;
		this.previousResponse = Optional.ofNullable(previousResponse);
		this.headers = response.headers();
		// this.trailers = trailers;
		this.sslSession = Optional.ofNullable(response.getSSLSession());
		this.uri = response.request().uri();
		this.version = response.version();
		this.connection = connection(exch);
		this.stream = null;
		this.body = body;
	}

	private HttpConnection connection(Exchange<?> exch) {
		if (exch == null || exch.exchImpl == null) {
			assert responseCode == 407;
			return null; // case of Proxy 407
		}
		return exch.exchImpl.connection();
	}

	private ExchangeImpl<?> exchangeImpl() {
		return exchange != null ? exchange.exchImpl : stream;
	}

	@Override
	public int statusCode() {
		return responseCode;
	}

	@Override
	public HttpRequest request() {
		return initialRequest;
	}

	@Override
	public Optional<HttpResponse<T>> previousResponse() {
		return previousResponse;
	}

	@Override
	public HttpHeaders headers() {
		return headers;
	}

	@Override
	public T body() {
		return body;
	}

	@Override
	public Optional<SSLSession> sslSession() {
		return sslSession;
	}

	@Override
	public URI uri() {
		return uri;
	}

	@Override
	public void close() throws IOException {
		/**
		 * TODO
		 * 
		 * - Calling close() in connection will cause intermittent explosions. <br>
		 * - Directly returning to pool doesn't close streams correctly, making header
		 * parsing explode.
		 */
	}

	@Override
	public HttpClient.Version version() {
		return version;
	}

	// keepalive flag determines whether connection is closed or kept alive
	// by reading/skipping data

	/**
	 * Returns a RawChannel that may be used for WebSocket protocol.
	 * 
	 * @implNote This implementation does not support RawChannel over HTTP/2
	 *           connections.
	 * @return a RawChannel that may be used for WebSocket protocol.
	 * @throws UnsupportedOperationException if getting a RawChannel over this
	 *                                       connection is not supported.
	 * @throws IOException                   if an I/O exception occurs while
	 *                                       retrieving the channel.
	 */
	@Override
	public synchronized RawChannel rawChannel() throws IOException {
		if (rawchan == null) {
			ExchangeImpl<?> exchImpl = exchangeImpl();
			if (!(exchImpl instanceof Http1Exchange)) {
				// RawChannel is only used for WebSocket - and WebSocket
				// is not supported over HTTP/2 yet, so we should not come
				// here. Getting a RawChannel over HTTP/2 might be supported
				// in the future, but it would entail retrieving any left over
				// bytes that might have been read but not consumed by the
				// HTTP/2 connection.
				throw new UnsupportedOperationException("RawChannel is not supported over HTTP/2");
			}
			// Http1Exchange may have some remaining bytes in its
			// internal buffer.
			Supplier<ByteBuffer> initial = ((Http1Exchange<?>) exchImpl)::drainLeftOverBytes;
			rawchan = new RawChannelTube(connection, initial);
		}
		return rawchan;
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		String method = request().method();
		URI uri = request().uri();
		String uristring = uri == null ? "" : uri.toString();
		sb.append('(').append(method).append(" ").append(uristring).append(") ").append(statusCode());
		return sb.toString();
	}
}